/*
 * Copyright (C) 2016 The Android Open Source Project
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package androidx.room.processor

import COMMON
import androidx.kruth.assertThat
import androidx.room.DatabaseProcessingStep
import androidx.room.RoomKspProcessor
import androidx.room.RoomProcessor
import androidx.room.compiler.codegen.CodeLanguage
import androidx.room.compiler.processing.XType
import androidx.room.compiler.processing.XTypeElement
import androidx.room.compiler.processing.util.CompilationResultSubject
import androidx.room.compiler.processing.util.Source
import androidx.room.compiler.processing.util.XTestInvocation
import androidx.room.compiler.processing.util.compileFiles
import androidx.room.compiler.processing.util.compileFilesIntoJar
import androidx.room.parser.ParsedQuery
import androidx.room.parser.QueryType
import androidx.room.parser.Table
import androidx.room.processor.ProcessorErrors.invalidAutoMigrationSchema
import androidx.room.runKspTestWithK1
import androidx.room.runProcessorTestWithK1
import androidx.room.solver.query.result.EntityRowAdapter
import androidx.room.solver.query.result.PojoRowAdapter
import androidx.room.testing.context
import androidx.room.vo.Database
import androidx.room.vo.DatabaseView
import androidx.room.vo.ReadQueryMethod
import androidx.room.vo.Warning
import com.google.auto.service.processor.AutoServiceProcessor
import com.google.testing.junit.testparameterinjector.TestParameter
import com.google.testing.junit.testparameterinjector.TestParameterInjector
import java.io.File
import java.io.FileOutputStream
import java.net.URL
import java.net.URLClassLoader
import org.hamcrest.CoreMatchers.equalTo
import org.hamcrest.CoreMatchers.hasItems
import org.hamcrest.CoreMatchers.instanceOf
import org.hamcrest.CoreMatchers.`is`
import org.hamcrest.CoreMatchers.not
import org.hamcrest.CoreMatchers.notNullValue
import org.hamcrest.CoreMatchers.sameInstance
import org.hamcrest.MatcherAssert.assertThat
import org.junit.Ignore
import org.junit.Rule
import org.junit.Test
import org.junit.rules.TemporaryFolder
import org.junit.runner.RunWith
import org.mockito.Mockito.mock

@RunWith(TestParameterInjector::class)
class DatabaseProcessorTest {
    companion object {
        const val DATABASE_PREFIX =
            """
            package foo.bar;
            import androidx.room.*;
            """
        val DB1 =
            Source.java(
                "foo.bar.Db1",
                """
                $DATABASE_PREFIX
                @Database(entities = {Book.class}, version = 42)
                public abstract class Db1 extends RoomDatabase {
                    abstract BookDao bookDao();
                }
                """
            )
        val DB2 =
            Source.java(
                "foo.bar.Db2",
                """
                $DATABASE_PREFIX
                @Database(entities = {Book.class}, version = 42)
                public abstract class Db2 extends RoomDatabase {
                    abstract BookDao bookDao();
                }
                """
            )
        val DB3 =
            Source.java(
                "foo.bar.Db3",
                """
                $DATABASE_PREFIX
                @Database(entities = {Book.class}, version = 42)
                public abstract class Db3 extends RoomDatabase {
                }
                """
            )
        val USER =
            Source.java(
                "foo.bar.User",
                """
                package foo.bar;
                import androidx.room.*;
                @Entity
                public class User {
                    @PrimaryKey
                    int uid;
                    String name;
                }
                """
            )
        val USER_DAO =
            Source.java(
                "foo.bar.UserDao",
                """
                package foo.bar;
                import androidx.room.*;
                @Dao
                public interface UserDao {
                    @Query("SELECT * FROM user")
                    public java.util.List<User> loadAll();

                    @Insert
                    public void insert(User... users);

                    @Query("SELECT * FROM user where uid = :uid")
                    public User loadOne(int uid);

                    @TypeConverters(Converter.class)
                    @Query("SELECT * FROM user where uid = :uid")
                    public User loadWithConverter(int uid);

                    @Query("SELECT * FROM user where uid = :uid")
                    public Pojo loadOnePojo(int uid);

                    @Query("SELECT * FROM user")
                    public java.util.List<Pojo> loadAllPojos();

                    @TypeConverters(Converter.class)
                    @Query("SELECT * FROM user where uid = :uid")
                    public Pojo loadPojoWithConverter(int uid);

                    public static class Converter {
                        @TypeConverter
                        public static java.util.Date foo(Long input) {return null;}
                    }

                    public static class Pojo {
                        public int uid;
                    }
                }
                """
            )
        val BOOK =
            Source.java(
                "foo.bar.Book",
                """
                package foo.bar;
                import androidx.room.*;
                @Entity
                public class Book {
                    @PrimaryKey
                    int bookId;
                }
                """
            )
        val BOOK_DAO =
            Source.java(
                "foo.bar.BookDao",
                """
                package foo.bar;
                import androidx.room.*;
                @Dao
                public interface BookDao {
                    @Query("SELECT * FROM book")
                    public java.util.List<Book> loadAllBooks();
                    @Insert
                    public void insert(Book book);
                }
                """
            )

        val PUBLISHER =
            Source.java(
                "foo.bar.Publisher",
                """
                package foo.bar;
                import androidx.room.*;
                @Entity
                public class Publisher {
                    @PrimaryKey
                    int publisherId;
                }
            """
            )

        val PUBLISHER_DAO =
            Source.java(
                "foo.bar.PublisherDao",
                """
                package foo.bar;
                import androidx.room.*;
                @Dao
                public interface PublisherDao {
                    @Upsert
                    public void upsert(Publisher publisher);
                }
            """
            )

        val AUTOMIGRATION =
            Source.java(
                "foo.bar.MyAutoMigration",
                """
            package foo.bar;
            import androidx.annotation.NonNull;
            import androidx.room.migration.AutoMigrationSpec;
            import androidx.sqlite.db.SupportSQLiteDatabase;
            class MyAutoMigration implements AutoMigrationSpec {
                @Override
                public void onPostMigrate(@NonNull SupportSQLiteDatabase db) {}
            }
            """
            )
    }

    @get:Rule var schemaFolder: TemporaryFolder = TemporaryFolder()

    @Test
    fun simple() {
        singleDb(
            """
            @Database(entities = {User.class}, version = 42)
            public abstract class MyDb extends RoomDatabase {
                abstract UserDao userDao();
            }
            """,
            USER,
            USER_DAO
        ) { db, _ ->
            assertThat(db.daoMethods.size, `is`(1))
            assertThat(db.entities.size, `is`(1))
        }
    }

    @Test
    fun multiple() {
        singleDb(
            """
            @Database(entities = {User.class, Book.class}, version = 42)
            public abstract class MyDb extends RoomDatabase {
                abstract UserDao userDao();
                abstract BookDao bookDao();
            }
            """,
            USER,
            USER_DAO,
            BOOK,
            BOOK_DAO
        ) { db, _ ->
            assertThat(db.daoMethods.size, `is`(2))
            assertThat(db.entities.size, `is`(2))
            assertThat(db.daoMethods.map { it.element.jvmName }, `is`(listOf("userDao", "bookDao")))
            assertThat(
                db.entities.map { it.type.asTypeName().toString(CodeLanguage.JAVA) },
                `is`(listOf("foo.bar.User", "foo.bar.Book"))
            )
        }
    }

    @Test
    fun detectMissingBaseClass() {
        singleDb(
            """
            @Database(entities = {User.class, Book.class}, version = 42)
            public abstract class MyDb {
            }
            """,
            USER,
            BOOK
        ) { _, invocation ->
            invocation.assertCompilationResult {
                hasErrorContaining(ProcessorErrors.DB_MUST_EXTEND_ROOM_DB)
            }
        }
    }

    @Test
    fun detectMissingTable() {
        singleDb(
            """
                @Database(entities = {Book.class}, version = 42)
                public abstract class MyDb extends RoomDatabase {
                    abstract BookDao bookDao();
                }
                """,
            BOOK,
            Source.java(
                "foo.bar.BookDao",
                """
                package foo.bar;
                import androidx.room.*;
                @Dao
                public interface BookDao {
                    @Query("SELECT * FROM nonExistentTable")
                    public java.util.List<Book> loadAllBooks();
                }
                """
            )
        ) { _, invocation ->
            invocation.assertCompilationResult {
                hasErrorContaining("no such table: nonExistentTable")
            }
        }
    }

    @Test
    fun detectDuplicateTableNames() {
        singleDb(
            """
                @Database(entities = {User.class, AnotherClass.class}, version = 42)
                public abstract class MyDb extends RoomDatabase {
                    abstract UserDao userDao();
                }
                """,
            USER,
            USER_DAO,
            Source.java(
                "foo.bar.AnotherClass",
                """
                package foo.bar;
                import androidx.room.*;
                @Entity(tableName="user")
                public class AnotherClass {
                    @PrimaryKey
                    int uid;
                }
                """
            )
        ) { _, invocation ->
            invocation.assertCompilationResult {
                hasErrorContaining(
                    ProcessorErrors.duplicateTableNames(
                        "user",
                        listOf("foo.bar.User", "foo.bar.AnotherClass")
                    )
                )
            }
        }
    }

    @Test
    @Ignore("b/285140651")
    fun detectMissingEntityAnnotationInLibraryClass() {
        val librarySource =
            Source.java(
                "test.library.MissingEntityAnnotationPojo",
                """
            package test.library;
            import androidx.room.*;
            public class MissingEntityAnnotationPojo {
                @PrimaryKey
                private long id;

                public void setId(int id) {this.id = id;}
                public long getId() {return this.id;}
            }
            """
                    .trimIndent()
            )
        val libraryClasspath = compileFiles(sources = listOf(librarySource))
        singleDb(
            """
                @Database(entities = {test.library.MissingEntityAnnotationPojo.class}, version = 1)
                public abstract class MyDb extends RoomDatabase {}
                """,
            classpath = libraryClasspath
        ) { _, invocation ->
            invocation.assertCompilationResult {
                compilationDidFail()
                hasRawOutputContaining(
                    ProcessorErrors.ENTITY_MUST_BE_ANNOTATED_WITH_ENTITY +
                        " - test.library.MissingEntityAnnotationPojo"
                )
            }
        }
    }

    @Test
    @Ignore("b/285140651")
    fun detectMissingDaoAnnotationInLibraryClass() {
        val librarySource =
            Source.java(
                "test.library.MissingAnnotationsBaseDao",
                """
            package test.library;
            import androidx.room.*;
            public interface MissingAnnotationsBaseDao {
                int getFoo();
            }
            """
                    .trimIndent()
            )
        val libraryClasspath = compileFiles(sources = listOf(librarySource))
        singleDb(
            """
                @Database(entities = {User.class}, version = 1)
                public abstract class MyDb extends RoomDatabase {
                    abstract test.library.MissingAnnotationsBaseDao getBadDao();
                }
                """,
            USER,
            classpath = libraryClasspath
        ) { _, invocation ->
            invocation.assertCompilationResult {
                compilationDidFail()
                hasRawOutputContaining(
                    ProcessorErrors.DAO_MUST_BE_ANNOTATED_WITH_DAO +
                        " - test.library.MissingAnnotationsBaseDao"
                )
            }
        }
    }

    @Test
    fun detectMissingExternalContentEntity() {
        val userNameFtsSrc =
            Source.java(
                "foo.bar.UserNameFts",
                """
                package foo.bar;
                import androidx.room.*;

                @Entity
                @Fts4(contentEntity = User.class)
                public class UserNameFts {
                    String name;
                }
                """
            )
        singleDb(
            """
                @Database(entities = {UserNameFts.class}, version = 1)
                public abstract class MyDb extends RoomDatabase {}
                """,
            USER,
            userNameFtsSrc
        ) { _, invocation ->
            invocation.assertCompilationResult {
                hasErrorContaining(
                    ProcessorErrors.missingExternalContentEntity(
                        "foo.bar.UserNameFts",
                        "foo.bar.User"
                    )
                )
            }
        }
    }

    @Test
    fun skipBadQueryVerification() {
        singleDb(
            """
                @SkipQueryVerification
                @Database(entities = {Book.class}, version = 42)
                public abstract class MyDb extends RoomDatabase {
                    abstract BookDao bookDao();
                }
                """,
            BOOK,
            Source.java(
                "foo.bar.BookDao",
                """
                package foo.bar;
                import androidx.room.*;
                @Dao
                public interface BookDao {
                    @Query("SELECT nonExistingField FROM Book")
                    public java.util.List<Book> loadAllBooks();
                }
                """
            )
        ) { _, _ ->
        }
    }

    @Test
    fun multipleDatabases() {
        val db1_2 =
            Source.java(
                "foo.barx.Db1",
                """
                package foo.barx;
                import androidx.room.*;
                import foo.bar.*;
                @Database(entities = {Book.class}, version = 42)
                public abstract class Db1 extends RoomDatabase {
                    abstract BookDao bookDao();
                }
                """
            )
        runProcessorTestWithK1(
            sources = listOf(BOOK, BOOK_DAO, DB1, DB2, db1_2),
            options = mapOf(Context.BooleanProcessorOptions.GENERATE_KOTLIN.argName to "false"),
            createProcessingSteps = { listOf(DatabaseProcessingStep()) }
        ) { result ->
            result.generatedSourceFileWithPath("foo/bar/Db1_Impl.java")
            result.generatedSourceFileWithPath("foo/bar/Db2_Impl.java")
            result.generatedSourceFileWithPath("foo/barx/Db1_Impl.java")
            result.generatedSourceFileWithPath("foo/bar/BookDao_Db1_0_Impl.java")
            result.generatedSourceFileWithPath("foo/bar/BookDao_Db1_1_Impl.java")
            result.generatedSourceFileWithPath("foo/bar/BookDao_Db2_Impl.java")
        }
    }

    @Test
    fun twoDaoMethodsForTheSameDao() {
        singleDb(
            """
                @Database(entities = {User.class}, version = 42)
                public abstract class MyDb extends RoomDatabase {
                    abstract UserDao userDao();
                    abstract UserDao userDao2();
                }
                """,
            USER,
            USER_DAO
        ) { _, invocation ->
            invocation.assertCompilationResult {
                hasErrorContaining(ProcessorErrors.DAO_METHOD_CONFLICTS_WITH_OTHERS)
                hasErrorContaining(
                    ProcessorErrors.duplicateDao("foo.bar.UserDao", listOf("userDao", "userDao2"))
                )
            }
        }
    }

    @Test
    fun suppressedWarnings() {
        singleDb(
            """
                @SuppressWarnings(RoomWarnings.QUERY_MISMATCH)
                @Database(entities = {User.class}, version = 42)
                public abstract class MyDb extends RoomDatabase {
                    abstract UserDao userDao();
                }
                """,
            USER,
            USER_DAO
        ) { db, invocation ->
            assertThat(
                DatabaseProcessor(invocation.context, db.element).context.logger.suppressedWarnings,
                `is`(setOf(Warning.QUERY_MISMATCH))
            )
        }
    }

    @Test
    fun duplicateIndexNames() {
        val entity1 =
            Source.java(
                "foo.bar.Entity1",
                """
                package foo.bar;
                import androidx.room.*;
                @Entity(indices = {@Index(name ="index_name", value = {"name"})})
                public class Entity1 {
                    @PrimaryKey
                    int uid;
                    String name;
                }
                """
            )

        val entity2 =
            Source.java(
                "foo.bar.Entity2",
                """
                package foo.bar;
                import androidx.room.*;
                @Entity(indices = {@Index(name ="index_name", value = {"anotherName"})})
                public class Entity2 {
                    @PrimaryKey
                    int uid;
                    String anotherName;
                }
                """
            )
        singleDb(
            """
                @Database(entities = {Entity1.class, Entity2.class}, version = 42)
                public abstract class MyDb extends RoomDatabase {
                }
                """,
            entity1,
            entity2
        ) { _, invocation ->
            invocation.assertCompilationResult {
                hasErrorContaining(
                    ProcessorErrors.duplicateIndexInDatabase(
                        "index_name",
                        listOf("foo.bar.Entity1 > index_name", "foo.bar.Entity2 > index_name")
                    )
                )
            }
        }
    }

    @Test
    fun foreignKey_missingParent() {
        val entity1 =
            Source.java(
                "foo.bar.Entity1",
                """
                package foo.bar;
                import androidx.room.*;
                @Entity(foreignKeys = @ForeignKey(
                        entity = ${COMMON.USER_TYPE_NAME.canonicalName}.class,
                        parentColumns = "lastName",
                        childColumns = "name"))
                public class Entity1 {
                    @PrimaryKey
                    int uid;
                    String name;
                }
                """
            )
        singleDb(
            """
                @Database(entities = {Entity1.class}, version = 42)
                public abstract class MyDb extends RoomDatabase {
                }
                """,
            entity1,
            COMMON.USER
        ) { _, invocation ->
            invocation.assertCompilationResult {
                hasErrorContaining(
                    ProcessorErrors.foreignKeyMissingParentEntityInDatabase(
                        "User",
                        "foo.bar.Entity1"
                    )
                )
            }
        }
    }

    @Test
    fun foreignKey_missingParentIndex() {
        val entity1 =
            Source.java(
                "foo.bar.Entity1",
                """
                package foo.bar;
                import androidx.room.*;
                @Entity(foreignKeys = @ForeignKey(
                        entity = ${COMMON.USER_TYPE_NAME.canonicalName}.class,
                        parentColumns = "lastName",
                        childColumns = "name"))
                public class Entity1 {
                    @PrimaryKey
                    int uid;
                    String name;
                }
                """
            )
        singleDb(
            """
                @Database(entities = {Entity1.class, User.class}, version = 42)
                public abstract class MyDb extends RoomDatabase {
                }
                """,
            entity1,
            COMMON.USER
        ) { _, invocation ->
            invocation.assertCompilationResult {
                hasErrorContaining(
                    ProcessorErrors.foreignKeyMissingIndexInParent(
                        parentEntity = COMMON.USER_TYPE_NAME.canonicalName,
                        parentColumns = listOf("lastName"),
                        childEntity = "foo.bar.Entity1",
                        childColumns = listOf("name")
                    )
                )
            }
        }
    }

    @Test
    fun foreignKey_goodWithPrimaryKey() {
        val entity1 =
            Source.java(
                "foo.bar.Entity1",
                """
                package foo.bar;
                import androidx.room.*;
                @Entity(foreignKeys = @ForeignKey(entity = Entity2.class,
                    parentColumns = "uid",
                    childColumns = "parentId"))
                public class Entity1 {
                    @PrimaryKey
                    int uid;
                    int parentId;
                    String name;
                }
                """
            )

        val entity2 =
            Source.java(
                "foo.bar.Entity2",
                """
                package foo.bar;
                import androidx.room.*;
                @Entity
                public class Entity2 {
                    @PrimaryKey
                    int uid;
                    String anotherName;
                }
                """
            )
        singleDb(
            """
                @Database(entities = {Entity1.class, Entity2.class}, version = 42)
                public abstract class MyDb extends RoomDatabase {
                }
                """,
            entity1,
            entity2
        ) { _, _ ->
        }
    }

    @Test
    fun foreignKey_missingParentColumn() {
        val entity1 =
            Source.java(
                "foo.bar.Entity1",
                """
                package foo.bar;
                import androidx.room.*;
                @Entity(foreignKeys = @ForeignKey(entity = Entity2.class,
                    parentColumns = {"anotherName", "anotherName2"},
                    childColumns = {"name", "name2"}))
                public class Entity1 {
                    @PrimaryKey
                    int uid;
                    String name;
                    String name2;
                }
                """
            )

        val entity2 =
            Source.java(
                "foo.bar.Entity2",
                """
                package foo.bar;
                import androidx.room.*;
                @Entity
                public class Entity2 {
                    @PrimaryKey
                    int uid;
                    String anotherName;
                }
                """
            )
        singleDb(
            """
                @Database(entities = {Entity1.class, Entity2.class}, version = 42)
                public abstract class MyDb extends RoomDatabase {
                }
                """,
            entity1,
            entity2
        ) { _, invocation ->
            invocation.assertCompilationResult {
                hasErrorContaining(
                    ProcessorErrors.foreignKeyParentColumnDoesNotExist(
                        "foo.bar.Entity2",
                        "anotherName2",
                        listOf("uid", "anotherName")
                    )
                )
            }
        }
    }

    @Test
    fun foreignKey_goodWithIndex() {
        val entity1 =
            Source.java(
                "foo.bar.Entity1",
                """
                package foo.bar;
                import androidx.room.*;
                @Entity(foreignKeys = @ForeignKey(entity = Entity2.class,
                    parentColumns = {"anotherName", "anotherName2"},
                    childColumns = {"name", "name2"}))
                public class Entity1 {
                    @PrimaryKey
                    int uid;
                    String name;
                    String name2;
                }
                """
            )

        val entity2 =
            Source.java(
                "foo.bar.Entity2",
                """
                package foo.bar;
                import androidx.room.*;
                @Entity(indices = @Index(value = {"anotherName2", "anotherName"}, unique = true))
                public class Entity2 {
                    @PrimaryKey
                    int uid;
                    String anotherName;
                    String anotherName2;
                }
                """
            )
        singleDb(
            """
                @Database(entities = {Entity1.class, Entity2.class}, version = 42)
                public abstract class MyDb extends RoomDatabase {
                }
                """,
            entity1,
            entity2
        ) { _, _ ->
        }
    }

    @Test
    fun insertNotAReferencedEntity() {
        singleDb(
            """
                @Database(entities = {User.class}, version = 42)
                public abstract class MyDb extends RoomDatabase {
                    abstract BookDao bookDao();
                }
                """,
            USER,
            USER_DAO,
            BOOK,
            BOOK_DAO
        ) { _, invocation ->
            invocation.assertCompilationResult {
                hasErrorContaining(
                    ProcessorErrors.shortcutEntityIsNotInDatabase(
                        database = "foo.bar.MyDb",
                        dao = "foo.bar.BookDao",
                        entity = "foo.bar.Book"
                    )
                )
            }
        }
    }

    @Test
    fun upsertNotReferencedEntity() {
        singleDb(
            """
                @Database(entities = {User.class}, version = 42)
                public abstract class MyDb extends RoomDatabase {
                    abstract PublisherDao publisherDao();
                }
            """,
            USER,
            USER_DAO,
            PUBLISHER,
            PUBLISHER_DAO
        ) { _, invocation ->
            invocation.assertCompilationResult {
                hasErrorContaining(
                    ProcessorErrors.shortcutEntityIsNotInDatabase(
                        database = "foo.bar.MyDb",
                        dao = "foo.bar.PublisherDao",
                        entity = "foo.bar.Publisher"
                    )
                )
            }
        }
    }

    @Test
    fun cache_entity() {
        singleDb(
            """
                @Database(entities = {User.class}, version = 42)
                @SkipQueryVerification
                public abstract class MyDb extends RoomDatabase {
                    public abstract MyUserDao userDao();
                    @Dao
                    interface MyUserDao {
                        @Insert
                        public void insert(User... users);

                        @Query("SELECT * FROM user where uid = :uid")
                        public User loadOne(int uid);

                        @TypeConverters(Converter.class)
                        @Query("SELECT * FROM user where uid = :uid")
                        public User loadWithConverter(int uid);
                    }
                    public static class Converter {
                        @TypeConverter
                        public static java.util.Date foo(Long input) {return null;}
                    }
                }
                """,
            USER,
            USER_DAO
        ) { db, _ ->
            val userDao = db.daoMethods.first().dao
            val insertionMethod = userDao.insertMethods.find { it.element.jvmName == "insert" }
            assertThat(insertionMethod, notNullValue())
            val loadOne =
                userDao.queryMethods.filterIsInstance<ReadQueryMethod>().find {
                    it.element.jvmName == "loadOne"
                }
            assertThat(loadOne, notNullValue())
            val adapter = loadOne?.queryResultBinder?.adapter?.rowAdapters?.single()
            assertThat("test sanity", adapter, instanceOf(EntityRowAdapter::class.java))
            val adapterEntity = (adapter as EntityRowAdapter).entity
            assertThat(
                adapterEntity,
                sameInstance(insertionMethod?.entities?.values?.first()?.pojo)
            )

            val withConverter =
                userDao.queryMethods.filterIsInstance<ReadQueryMethod>().find {
                    it.element.jvmName == "loadWithConverter"
                }
            assertThat(withConverter, notNullValue())
            val convAdapter = withConverter?.queryResultBinder?.adapter?.rowAdapters?.single()
            assertThat("test sanity", adapter, instanceOf(EntityRowAdapter::class.java))
            val convAdapterEntity = (convAdapter as EntityRowAdapter).entity
            assertThat(
                convAdapterEntity,
                not(sameInstance(insertionMethod?.entities?.values?.first()?.pojo))
            )

            assertThat(convAdapterEntity, notNullValue())
            assertThat(adapterEntity, notNullValue())
        }
    }

    @Test
    fun cache_pojo() {
        singleDb(
            """
                @Database(entities = {User.class}, version = 42)
                public abstract class MyDb extends RoomDatabase {
                    public abstract UserDao userDao();
                }
                """,
            USER,
            USER_DAO
        ) { db, _ ->
            val userDao = db.daoMethods.first().dao
            val loadOne =
                userDao.queryMethods.filterIsInstance<ReadQueryMethod>().find {
                    it.element.jvmName == "loadOnePojo"
                }
            assertThat(loadOne, notNullValue())
            val adapter = loadOne?.queryResultBinder?.adapter?.rowAdapters?.single()
            assertThat("test sanity", adapter, instanceOf(PojoRowAdapter::class.java))
            val adapterPojo = (adapter as PojoRowAdapter).pojo

            val loadAll =
                userDao.queryMethods.filterIsInstance<ReadQueryMethod>().find {
                    it.element.jvmName == "loadAllPojos"
                }
            assertThat(loadAll, notNullValue())
            val loadAllAdapter = loadAll?.queryResultBinder?.adapter?.rowAdapters?.single()
            assertThat("test sanity", loadAllAdapter, instanceOf(PojoRowAdapter::class.java))
            val loadAllPojo = (loadAllAdapter as PojoRowAdapter).pojo
            assertThat(adapter, not(sameInstance(loadAllAdapter)))
            assertThat(adapterPojo, sameInstance(loadAllPojo))

            val withConverter =
                userDao.queryMethods.filterIsInstance<ReadQueryMethod>().find {
                    it.element.jvmName == "loadPojoWithConverter"
                }
            assertThat(withConverter, notNullValue())
            val convAdapter = withConverter?.queryResultBinder?.adapter?.rowAdapters?.single()
            assertThat("test sanity", adapter, instanceOf(PojoRowAdapter::class.java))
            val convAdapterPojo = (convAdapter as PojoRowAdapter).pojo
            assertThat(convAdapterPojo, notNullValue())
            assertThat(convAdapterPojo, not(sameInstance(adapterPojo)))
        }
    }

    @Test
    fun daoConstructor_RoomDatabase() {
        assertConstructor(listOf(DB1), "BookDao(RoomDatabase db) {}")
    }

    @Test
    fun daoConstructor_specificDatabase() {
        assertConstructor(listOf(DB1), "BookDao(Db1 db) {}")
    }

    @Test
    fun daoConstructor_wrongDatabase() {
        assertConstructor(listOf(DB1, DB3), "BookDao(Db3 db) {}") { result ->
            result.hasErrorContaining(
                ProcessorErrors.daoMustHaveMatchingConstructor("foo.bar.BookDao", "foo.bar.Db1")
            )
        }
    }

    @Test
    fun daoConstructor_multipleDatabases_RoomDatabase() {
        assertConstructor(listOf(DB1, DB2), "BookDao(RoomDatabase db) {}")
    }

    @Test
    fun daoConstructor_multipleDatabases_specificDatabases() {
        assertConstructor(
            listOf(DB1, DB2),
            """
                    BookDao(Db1 db) {}
                    BookDao(Db2 db) {}
                """
        )
    }

    @Test
    fun daoConstructor_multipleDatabases_empty() {
        assertConstructor(
            listOf(DB1, DB2),
            """
                    BookDao(Db1 db) {}
                    BookDao() {} // Db2 uses this
                """
        )
    }

    @Test
    fun daoConstructor_multipleDatabases_noMatch() {
        assertConstructor(
            listOf(DB1, DB2),
            """
                    BookDao(Db1 db) {}
                """
        ) { result ->
            result.hasErrorContaining(
                ProcessorErrors.daoMustHaveMatchingConstructor("foo.bar.BookDao", "foo.bar.Db2")
            )
        }
    }

    @Test
    fun view_duplicateNames() {
        val view1 =
            Source.java(
                "foo.bar.View1",
                """
                package foo.bar;
                import androidx.room.*;
                @DatabaseView(value = "SELECT * FROM User", viewName = "SameName")
                public class View1 {}
                """
            )
        val view2 =
            Source.java(
                "foo.bar.View2",
                """
                package foo.bar;
                import androidx.room.*;
                @DatabaseView(value = "SELECT * FROM User", viewName = "SameName")
                public class View2 {}
                """
            )
        singleDb(
            """
                @Database(entities = {User.class},
                          views = {View1.class, View2.class},
                          version = 42)
                public abstract class MyDb extends RoomDatabase {
                }
        """,
            USER,
            view1,
            view2
        ) { _, invocation ->
            invocation.assertCompilationResult {
                hasErrorContaining(
                    ProcessorErrors.duplicateTableNames(
                        "samename",
                        listOf("foo.bar.View1", "foo.bar.View2")
                    )
                )
            }
        }
    }

    @Test
    fun view_duplicateNamesWithEntity() {
        val view1 =
            Source.java(
                "foo.bar.View1",
                """
                package foo.bar;
                import androidx.room.*;
                @DatabaseView(value = "SELECT * FROM User", viewName = "Book")
                public class View1 {}
                """
            )
        singleDb(
            """
                @Database(entities = {User.class, Book.class},
                          views = {View1.class},
                          version = 42)
                public abstract class MyDb extends RoomDatabase {
                }
        """,
            USER,
            BOOK,
            view1
        ) { _, invocation ->
            invocation.assertCompilationResult {
                hasErrorContaining(
                    ProcessorErrors.duplicateTableNames(
                        "book",
                        listOf("foo.bar.Book", "foo.bar.View1")
                    )
                )
            }
        }
    }

    @Test
    fun view_circularReference() {
        val view1 =
            Source.java(
                "foo.bar.View1",
                """
                package foo.bar;
                import androidx.room.*;
                @DatabaseView("SELECT * FROM View2")
                public class View1 {}
                """
            )
        val view2 =
            Source.java(
                "foo.bar.View2",
                """
                package foo.bar;
                import androidx.room.*;
                @DatabaseView("SELECT * FROM View1")
                public class View2 {}
                """
            )
        singleDb(
            """
                @Database(entities = {User.class},
                          views = {View1.class, View2.class},
                          version = 42)
                public abstract class MyDb extends RoomDatabase {
                }
        """,
            USER,
            view1,
            view2
        ) { _, invocation ->
            invocation.assertCompilationResult {
                hasErrorContaining(
                    ProcessorErrors.viewCircularReferenceDetected(listOf("View1", "View2"))
                )
            }
        }
    }

    @Test
    fun resolveDatabaseViews_nested() {
        resolveDatabaseViews(
            mapOf(
                "Q" to setOf("B", "P"),
                "P" to setOf("A"),
                "S" to setOf("A", "Q"),
                "R" to setOf("C", "Q")
            )
        ) { views, _ ->
            assertThat(views.size, `is`(4))
            views[0].let {
                assertThat(it.viewName, `is`(equalTo("P")))
                assertThat(it.tables.size, `is`(1))
                assertThat(it.tables, hasItems("A"))
            }
            views[1].let {
                assertThat(it.viewName, `is`(equalTo("Q")))
                assertThat(it.tables.size, `is`(2))
                assertThat(it.tables, hasItems("A", "B"))
            }
            // The order here is not important.
            val (viewR, viewS) =
                if (views[2].viewName == "R") {
                    listOf(views[2], views[3])
                } else {
                    listOf(views[3], views[2])
                }
            viewR.let {
                assertThat(it.viewName, `is`(equalTo("R")))
                assertThat(it.tables.size, `is`(3))
                assertThat(it.tables, hasItems("A", "B", "C"))
            }
            viewS.let {
                assertThat(it.viewName, `is`(equalTo("S")))
                assertThat(it.tables.size, `is`(2))
                assertThat(it.tables, hasItems("A", "B"))
            }
        }
    }

    @Test
    fun resolveDatabaseViews_empty() {
        resolveDatabaseViews(emptyMap()) { views, _ -> assertThat(views.size, `is`(0)) }
    }

    @Test
    fun resolveDatabaseViews_circular() {
        resolveDatabaseViews(
            mapOf("P" to setOf("Q"), "Q" to setOf("P"), "R" to setOf("A"), "S" to setOf("R", "B"))
        ) { _, invocation ->
            invocation.assertCompilationResult {
                hasErrorContaining(ProcessorErrors.viewCircularReferenceDetected(listOf("P", "Q")))
            }
        }
    }

    @Test
    fun daoMethod_nonDeclaredReturnType() {
        val badDaoType =
            Source.java(
                "foo.bar.MyDb",
                """
                package foo.bar;
                import androidx.room.*;
                @Database(version = 1, entities = {})
                public abstract class MyDb extends RoomDatabase {
                    abstract long getDao();
                }
                """
            )
        runProcessorTestWithK1(sources = listOf(badDaoType)) { invocation ->
            val element = invocation.processingEnv.requireTypeElement("foo.bar.MyDb")
            val result =
                DatabaseProcessor(baseContext = invocation.context, element = element).process()
            assertThat(result.daoMethods).hasSize(0)
            invocation.assertCompilationResult {
                hasErrorContaining(ProcessorErrors.DATABASE_INVALID_DAO_METHOD_RETURN_TYPE)
            }
        }
    }

    @Test
    fun nonDeclaredEntity() {
        val badDaoType =
            Source.java(
                "foo.bar.MyDb",
                """
                package foo.bar;
                import androidx.room.*;
                @Database(version = 1, entities = {long.class}, views = {int.class})
                public abstract class MyDb extends RoomDatabase {
                }
                """
            )
        runProcessorTestWithK1(listOf(badDaoType)) { invocation ->
            val element = invocation.processingEnv.requireTypeElement("foo.bar.MyDb")
            val result =
                DatabaseProcessor(baseContext = invocation.context, element = element).process()
            assertThat(result.entities).hasSize(if (invocation.isKsp) 1 else 0)
            assertThat(result.views).hasSize(if (invocation.isKsp) 1 else 0)
            if (invocation.isKsp) {
                invocation.assertCompilationResult {
                    hasErrorContaining(ProcessorErrors.ENTITY_MUST_BE_ANNOTATED_WITH_ENTITY)
                    hasErrorContaining(ProcessorErrors.VIEW_MUST_BE_ANNOTATED_WITH_DATABASE_VIEW)
                }
            } else {
                invocation.assertCompilationResult {
                    hasErrorContaining(
                        ProcessorErrors.invalidEntityTypeInDatabaseAnnotation("long")
                    )
                    hasErrorContaining(ProcessorErrors.invalidViewTypeInDatabaseAnnotation("int"))
                }
            }
        }
    }

    @Test
    fun autoMigrationDefinedButDatabaseSchemaExportOff() {
        singleDb(
            """
                @Database(entities = {User.class}, version = 42, exportSchema = false,
                autoMigrations = {@AutoMigration(from = 1, to = 2, spec = MyAutoMigration
                .class), @AutoMigration(from = 2, to = 3)})
                public abstract class MyDb extends RoomDatabase {}
                """,
            USER,
            AUTOMIGRATION
        ) { _, invocation ->
            invocation.assertCompilationResult {
                hasErrorContaining(ProcessorErrors.AUTO_MIGRATION_FOUND_BUT_EXPORT_SCHEMA_OFF)
            }
        }
    }

    @Test
    fun autoMigrationSchemasNotFound() {
        singleDb(
            """
                @Database(entities = {User.class}, version = 42, exportSchema = true,
                autoMigrations = {@AutoMigration(from = 1, to = 2, spec = MyAutoMigration
                .class)})
                public abstract class MyDb extends RoomDatabase {}
                """,
            USER,
            AUTOMIGRATION
        ) { _, invocation ->
            invocation.assertCompilationResult {
                hasErrorCount(1)
                hasErrorContaining(
                    ProcessorErrors.autoMigrationSchemasNotFound(
                        1,
                        schemaFolder.root.absolutePath + File.separator + "foo.bar.MyDb"
                    )
                )
            }
        }
    }

    @Test
    fun autoMigrationFromSchemaNotFound() {
        schemaFolder.newFolder("foo.bar.MyDb")
        schemaFolder.newFile("foo.bar.MyDb" + File.separator + "2.json")
        singleDb(
            """
                @Database(entities = {User.class}, version = 42, exportSchema = true,
                autoMigrations = {@AutoMigration(from = 1, to = 2, spec = MyAutoMigration
                .class)})
                public abstract class MyDb extends RoomDatabase {}
                """,
            USER,
            AUTOMIGRATION,
        ) { _, invocation ->
            invocation.assertCompilationResult {
                hasErrorCount(1)
                hasErrorContaining(
                    ProcessorErrors.autoMigrationSchemasNotFound(
                        1,
                        schemaFolder.root.absolutePath + File.separator + "foo.bar.MyDb"
                    )
                )
            }
        }
    }

    @Test
    fun autoMigrationToSchemaNotFound() {
        schemaFolder.newFolder("foo.bar.MyDb")
        singleDb(
            """
                @Database(entities = {User.class}, version = 42, exportSchema = true,
                autoMigrations = {@AutoMigration(from = 1, to = 2, spec = MyAutoMigration
                .class)})
                public abstract class MyDb extends RoomDatabase {}
                """,
            USER,
            AUTOMIGRATION
        ) { _, invocation ->
            invocation.assertCompilationResult {
                hasErrorCount(1)
                hasErrorContaining(
                    ProcessorErrors.autoMigrationSchemasNotFound(
                        1,
                        schemaFolder.root.absolutePath + File.separator + "foo.bar.MyDb"
                    )
                )
            }
        }
    }

    @Test
    fun autoMigrationEmptySchemaFiles() {
        schemaFolder.newFolder("foo.bar.MyDb")
        schemaFolder.newFile("foo.bar.MyDb" + File.separator + "1.json")
        schemaFolder.newFile("foo.bar.MyDb" + File.separator + "2.json")

        singleDb(
            """
                @Database(entities = {User.class}, version = 42, exportSchema = true,
                autoMigrations = {@AutoMigration(from = 1, to = 2, spec = MyAutoMigration
                .class)})
                public abstract class MyDb extends RoomDatabase {}
                """,
            USER,
            AUTOMIGRATION,
        ) { _, invocation ->
            invocation.assertCompilationResult {
                hasErrorCount(2)
                hasErrorContaining("Unable to read schema file")
                hasErrorContaining(
                    ProcessorErrors.invalidAutoMigrationSchema(
                        1,
                        schemaFolder.root.absolutePath + File.separator + "foo.bar.MyDb"
                    )
                )
            }
        }
    }

    @Test
    fun allAutoMigrationSchemasProvidedButNotRoomGenerated() {
        val dbFolder = schemaFolder.newFolder("foo.bar.MyDb")
        val from: File = schemaFolder.newFile("foo.bar.MyDb" + File.separator + "1.json")
        val to: File = schemaFolder.newFile("foo.bar.MyDb" + File.separator + "2.json")
        FileOutputStream(from).bufferedWriter().use { it.write("BAD FILE") }
        FileOutputStream(to).bufferedWriter().use { it.write("BAD FILE") }
        singleDb(
            """
                @Database(entities = {User.class}, version = 42, exportSchema = true,
                autoMigrations = {@AutoMigration(from = 1, to = 2)})
                public abstract class MyDb extends RoomDatabase {}
                """,
            USER,
            AUTOMIGRATION,
        ) { _, invocation ->
            invocation.assertCompilationResult {
                hasErrorCount(2)
                hasErrorContaining("Unable to read schema file")
                hasErrorContaining(invalidAutoMigrationSchema(1, dbFolder.absolutePath))
            }
        }
    }

    @Test
    fun differentSchemaResolver() {
        val tempDirPath = schemaFolder.root.absolutePath
        val serviceSource =
            Source.java(
                "foo.bar.TestResolver",
                """
            package foo.bar;
            import androidx.room.util.SchemaFileResolver;
            import com.google.auto.service.AutoService;
            import java.io.File;
            import java.io.IOException;
            import java.io.InputStream;
            import java.io.OutputStream;
            import java.nio.file.Files;
            import java.nio.file.Path;
            @AutoService(SchemaFileResolver.class)
            public class TestResolver implements SchemaFileResolver {
                @Override
                public InputStream readPath(Path path) throws IOException {
                    Path resolved = Path.of("$tempDirPath").resolve(path);
                    if (Files.exists(resolved)) {
                        return Files.newInputStream(resolved);
                    } else {
                        return null;
                    }
                }
                @Override
                public OutputStream writePath(Path path) throws IOException {
                    Path resolved = Path.of("$tempDirPath").resolve(path);
                    Files.createDirectories(resolved.getParent());
                    return Files.newOutputStream(resolved);
                }
            }
            """
                    .trimIndent()
            )
        val resolverLib =
            compileFilesIntoJar(
                outputDirectory = schemaFolder.root,
                sources = listOf(serviceSource),
                annotationProcessors = listOf(AutoServiceProcessor()),
            )
        val dbSource =
            Source.java(
                "foo.bar.MyDb",
                """
            package foo.bar;
            import androidx.room.*;
            @Database(entities = {User.class}, version = 1, exportSchema = true)
            public abstract class MyDb extends RoomDatabase {}
            """
                    .trimIndent()
            )
        runProcessorTestWithK1(
            sources = listOf(dbSource, USER),
            options = mapOf("room.schemaLocation" to "schemas/", "room.generateKotlin" to "false")
        ) { invocation ->
            val dbAnnotationName = "androidx.room.Database"
            val roundElements =
                mapOf(
                    dbAnnotationName to
                        invocation.roundEnv.getElementsAnnotatedWith(dbAnnotationName)
                )
            // Temporarily change the current's thread class loader so that the ServiceLocator can
            // find the compiled TestResolver. It is not enough to pass the compiled jar in the
            // classpath argument when running processors because the processor classpath is
            // different from the user classpath and specifically the processor classes are already
            // loaded.
            val currentClassLoader = Thread.currentThread().contextClassLoader
            try {
                Thread.currentThread().contextClassLoader =
                    URLClassLoader(
                        arrayOf(URL("file://${resolverLib.absolutePath}")),
                        currentClassLoader,
                    )
                DatabaseProcessingStep().process(invocation.processingEnv, roundElements, false)
            } finally {
                Thread.currentThread().contextClassLoader = currentClassLoader
            }
        }
        // Assert schema file is created in a directory different from the room.schemaLocation
        // verbatim since it was resolved by the compiled TestResolver.
        assertThat(File(schemaFolder.root, "schemas/foo.bar.MyDb/1.json").exists()).isTrue()
    }

    @Test
    fun exportSchemaToJarResources(@TestParameter withKotlinSrc: Boolean) {
        val dbSource =
            if (withKotlinSrc) {
                Source.kotlin(
                    "foo/bar/MyDb.kt",
                    """
                    package foo.bar
                    import androidx.room.*
                    @Database(entities = [User::class], version = 1, exportSchema = true)
                    abstract class MyDb : RoomDatabase()
                    """
                        .trimIndent()
                )
            } else {
                Source.java(
                    "foo.bar.MyDb",
                    """
                    package foo.bar;
                    import androidx.room.*;
                    @Database(entities = {User.class}, version = 1, exportSchema = true)
                    public abstract class MyDb extends RoomDatabase {}
                    """
                        .trimIndent()
                )
            }
        runProcessorTestWithK1(
            sources = listOf(dbSource, USER),
            javacProcessors = listOf(RoomProcessor()),
            symbolProcessorProviders = listOf(RoomKspProcessor.Provider()),
            options = mapOf("room.exportSchemaResource" to "true"),
        ) {
            it.generatedResourceFileWithPath("schemas/foo.bar.MyDb/1.json")
        }
    }

    @Test
    fun jvmNameOnDaoMethod() {
        val jvmNameInDaoGetter =
            Source.kotlin(
                "MyDb.kt",
                """
                package foo.bar;
                import androidx.room.*;
                @Dao
                interface MyDao
                @Entity
                data class MyEntity(@PrimaryKey val id:Int)
                @Database(version = 1, entities = [MyEntity::class])
                abstract class MyDb: RoomDatabase() {
                    @Suppress("INAPPLICABLE_JVM_NAME")
                    @get:JvmName("jvmDao")
                    abstract val dao: MyDao
                }
                """
            )
        runProcessorTestWithK1(
            sources = listOf(jvmNameInDaoGetter),
            options = mapOf(Context.BooleanProcessorOptions.GENERATE_KOTLIN.argName to "false"),
        ) { invocation ->
            val element = invocation.processingEnv.requireTypeElement("foo.bar.MyDb")
            DatabaseProcessor(baseContext = invocation.context, element = element).process()
            invocation.assertCompilationResult {
                hasWarningContaining(ProcessorErrors.JVM_NAME_ON_OVERRIDDEN_METHOD)
            }
        }
    }

    @Test
    fun allowPropertyDao() {
        val src =
            Source.kotlin(
                "MyDatabase.kt",
                """
            import androidx.room.*

            @Database(entities = [MyEntity::class], version = 1, exportSchema = false)
            abstract class MyDatabase : RoomDatabase() {
              abstract val dao: MyDao
            }

            @Dao
            interface MyDao {
              @Query("SELECT * FROM MyEntity")
              fun getEntity(): MyEntity
            }

            @Entity
            data class MyEntity(
                @PrimaryKey
                var pk: Int
            )
            """
                    .trimIndent()
            )
        runKspTestWithK1(
            sources = listOf(src),
            options = mapOf(Context.BooleanProcessorOptions.GENERATE_KOTLIN.argName to "true"),
        ) { invocation ->
            val element = invocation.processingEnv.requireTypeElement("MyDatabase")
            DatabaseProcessor(baseContext = invocation.context, element = element).process()
            invocation.assertCompilationResult { hasNoWarnings() }
        }
    }

    @Test
    fun invalidVersion() {
        singleDb(
            """
            @Database(entities = {User.class}, version = 0)
            public abstract class MyDb extends RoomDatabase {
                abstract UserDao userDao();
            }
            """,
            USER,
            USER_DAO
        ) { _, invocation ->
            invocation.assertCompilationResult {
                hasErrorCount(1)
                hasErrorContaining(ProcessorErrors.INVALID_DATABASE_VERSION)
            }
        }
    }

    private fun resolveDatabaseViews(
        views: Map<String, Set<String>>,
        body: (List<DatabaseView>, XTestInvocation) -> Unit
    ) {
        runProcessorTestWithK1(
            sources = listOf(DB3, BOOK),
            options = mapOf(Context.BooleanProcessorOptions.GENERATE_KOTLIN.argName to "false"),
        ) { invocation ->
            val database =
                invocation.roundEnv
                    .getElementsAnnotatedWith(androidx.room.Database::class.qualifiedName!!)
                    .filterIsInstance<XTypeElement>()
                    .first()
            val processor = DatabaseProcessor(invocation.context, database)

            val list =
                views.map { (viewName, names) ->
                    DatabaseView(
                        element = mock(XTypeElement::class.java),
                        viewName = viewName,
                        query =
                            ParsedQuery(
                                "",
                                QueryType.SELECT,
                                emptyList(),
                                names.map { Table(it, it) }.toSet(),
                                null,
                                emptyList()
                            ),
                        type = mock(XType::class.java),
                        fields = emptyList(),
                        embeddedFields = emptyList(),
                        constructor = null
                    )
                }
            val resolvedViews = processor.resolveDatabaseViews(list)
            body(resolvedViews, invocation)
        }
    }

    private fun assertConstructor(
        dbs: List<Source>,
        constructor: String,
        onCompilationResult: ((result: CompilationResultSubject) -> Unit)? = null
    ) {
        val bookDao =
            Source.java(
                "foo.bar.BookDao",
                """
                package foo.bar;
                import androidx.room.*;
                @Dao
                public abstract class BookDao {
                    $constructor
                }
                """
            )
        runProcessorTestWithK1(
            sources = listOf(BOOK, bookDao) + dbs,
            options = mapOf(Context.BooleanProcessorOptions.GENERATE_KOTLIN.argName to "false"),
            createProcessingSteps = { listOf(DatabaseProcessingStep()) },
        ) {
            onCompilationResult?.invoke(it)
        }
    }

    private fun singleDb(
        input: String,
        vararg otherFiles: Source,
        classpath: List<File> = emptyList(),
        handler: (Database, XTestInvocation) -> Unit
    ) {
        runProcessorTestWithK1(
            sources = otherFiles.toList() + Source.java("foo.bar.MyDb", DATABASE_PREFIX + input),
            classpath = classpath,
            options =
                mapOf(
                    "room.schemaLocation" to schemaFolder.root.absolutePath,
                    Context.BooleanProcessorOptions.GENERATE_KOTLIN.argName to "false"
                )
        ) { invocation ->
            val entity =
                invocation.roundEnv
                    .getElementsAnnotatedWith(androidx.room.Database::class.qualifiedName!!)
                    .filterIsInstance<XTypeElement>()
                    .first()
            val parser = DatabaseProcessor(invocation.context, entity)
            val parsedDb = parser.process()
            handler(parsedDb, invocation)
        }
    }
}
